package com.myframework.utils.configutil;

import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.HashMap;

import org.xml.sax.EntityResolver;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;

import com.myframework.utils.logutil.WMLogger;
import com.myframework.utils.stringutil.StringUtil;

/**
 * Implementation of the EntityResolver to provide locally source resources for
 * off-line resolution of DTDs for XML validation.
 */
public class DTDResolver implements EntityResolver {

	private static HashMap<String, String> resolvedResources	= new HashMap<String, String>();

	/**
	 * Allow the application to resolve external entities.
	 * <p/>
	 * <p>
	 * The parser will call this method before opening any external entity except
	 * the top-level document entity. Such entities include the external DTD
	 * subset and external parameter entities referenced within the DTD (in either
	 * case, only if the parser reads external parameter entities), and external
	 * general entities referenced within the document element (if the parser
	 * reads external general entities). The application may request that the
	 * parser locate the entity itself, that it use an alternative URI, or that it
	 * use data provided by the application (as a character or byte input stream).
	 * </p>
	 * <p/>
	 * <p>
	 * Application writers can use this method to redirect external system
	 * identifiers to secure and/or local URIs, to look up public identifiers in a
	 * catalogue, or to read an entity from a database or other input source
	 * (including, for example, a dialog box). Neither XML nor SAX specifies a
	 * preferred policy for using public or system IDs to resolve resources.
	 * However, SAX specifies how to interpret any InputSource returned by this
	 * method, and that if none is returned, then the system ID will be
	 * dereferenced as a URL.
	 * </p>
	 * <p/>
	 * <p>
	 * If the system identifier is a URL, the SAX parser must resolve it fully
	 * before reporting it to the application.
	 * </p>
	 * 
	 * @param publicId
	 *          The public identifier of the external entity being referenced, or
	 *          null if none was supplied.
	 * @param systemId
	 *          The system identifier of the external entity being referenced.
	 * @return An InputSource object describing the new input source, or null to
	 *         request that the parser open a regular URI connection to the system
	 *         identifier.
	 * @throws org.xml.sax.SAXException
	 *           Any SAX exception, possibly wrapping another exception.
	 * @throws java.io.IOException
	 *           A Java-specific IO exception, possibly the result of creating a
	 *           new InputStream or Reader for the InputSource.
	 * @see org.xml.sax.InputSource
	 */
	@Override
	public InputSource resolveEntity(String publicId, String systemId) throws SAXException, IOException {
		InputSource inputSource = null;
		StringBuffer sb = new StringBuffer();

		// Check if we have a local resource first
		if (resolvedResources.containsKey(systemId)) {
			// return the resolved resource
			if (resolvedResources.get(systemId) != null) {
				inputSource = new InputSource(DTDResolver.class.getResourceAsStream((String) resolvedResources.get(systemId)));
			} else {
				// If we have a null systemId, it means we've looked before and were
				// unable to find it so return the null input source to trigger the
				// default resolver
				return inputSource;
			}
		} else {
			// build a local path and look for the resource using the Class resource
			// loader
			String localPath = getLocalPath(systemId);
			sb.append("Attempting to resolve '" + systemId + "' locally at '").append(localPath).append("'");
			InputStream ios = DTDResolver.class.getResourceAsStream(localPath);
			if (ios != null) {
				// Resource was found and appears to be valid. Store the path in the map
				// for future validations
				inputSource = new InputSource(ios);
				resolvedResources.put(systemId, localPath);
				sb.append("... Found locally.");
			} else {
				sb.append("... FAILED.  Using the default resolver for '" + systemId + "'");
				resolvedResources.put(systemId, null);
			}
		}
		if (sb.length() > 0) {
			WMLogger.debug(sb.toString());
		}
		return inputSource;
	}

	/**
	 * Utility method to derive an acceptable package heirarchy to traverse
	 * looking for locally resolved resources.
	 * 
	 * @param systemId
	 *          The system identifier of the external entity being referenced.
	 * @return A derived local path use for resource resolution
	 */
	protected String getLocalPath(String systemId) {
		String localPath = null;
		try {
			URL url = new URL(systemId);
			String[] pathParts = StringUtil.split(url.getPath(), "/");
			localPath = "/META-INF/" + pathParts[pathParts.length - 1];
		} catch (MalformedURLException e) {
			// ignore?
		}
		return localPath;
	}

}
